package main

import (
	"context"
	"flag"
	//"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/wonderivan/logger"
	"go-http-server/metrics"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var (
	host       string
	port       int
	listenAddr string
)

type logWriterResponseWriter struct {
	http.ResponseWriter
	status int
}

func wrapResponseWriter(w http.ResponseWriter) *logWriterResponseWriter {
	return &logWriterResponseWriter{ResponseWriter: w}
}

func (rw *logWriterResponseWriter) Status() int {
	return rw.status
}

func (rw *logWriterResponseWriter) WriteHeader(code int) {
	rw.status = code
	rw.ResponseWriter.WriteHeader(code)
	return
}

func main() {
	flag.StringVar(&host, "h", "0.0.0.0", "server listen address")
	flag.IntVar(&port, "p", 3000, "server listen port")
	flag.Parse()

	logWriter := log.New(os.Stdout, "http_accesslog: ", log.LstdFlags)
	logWriter.Println("HTTP Server is starting...")
	metrics.Register()
	router := http.NewServeMux()
	router.Handle("/", index())
	router.Handle("/delay", delay())
	router.Handle("/healthz", healthz())
	router.Handle("/badService", badService())
	router.Handle("/metrics", promhttp.Handler())
	router.Handle("/tracing", tracing())

	listenAddr := strings.Join([]string{host, strconv.Itoa(port)}, ":")
	server := &http.Server{
		Addr:         listenAddr,
		Handler:      logging(logWriter)(router),
		ErrorLog:     logWriter,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  15 * time.Second,
	}

	done := make(chan bool)
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)

	go func() {
		<-quit
		logWriter.Println("HTTP Server is shutting down...")

		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()

		if err := server.Shutdown(ctx); err != nil {
			logWriter.Fatalf("Could not gracefully shutdown the server: %v\n", err)
		}

		close(done)
	}()

	logWriter.Println("HTTP Server is ready to handle requests at", listenAddr)
	if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		logWriter.Fatalf("Could not listen on %s: %v\n", listenAddr, err)
	}
	<-done
	logWriter.Println("HTTP Server stopped")
}

func addResponseHeader(w http.ResponseWriter, r *http.Request) {
	for k, v := range r.Header {
		reqHeader := strings.Join(v, " ")
		w.Header().Add(k, reqHeader)
	}
	if os.Getenv("VERSION") != "" {
		w.Header().Add("VERSION ", os.Getenv("VERSION"))
	}
}

func RandInt64(min, max int64) int64 {
	if min >= max || min == 0 || max == 0 {
		return max
	}
	return rand.Int63n(max-min) + min
}

func delay() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		timer := metrics.NewTimer()
		defer timer.ObserverTotal()
		delay := RandInt64(10, 2000)
		time.Sleep(time.Millisecond * time.Duration(delay))
		//addResponseHeader(w, r)
		w.WriteHeader(http.StatusOK)
		io.WriteString(w, "Delay function")
		logger.Info("Delay %d milliseconds", delay)
	})
}

func tracing() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		logger.Info("entering tracing handler")
		delay := RandInt64(13, 20)
		time.Sleep(time.Millisecond * time.Duration(delay))
		io.WriteString(w, "==============================Details of the http request header:==============================\n")
		req, err := http.NewRequest("GET", "http://go-http-srv02-service/tracing", nil)
		if err != nil {
			logger.Error("%s", err)
		}
		lowerCaseHeader := make(http.Header)
		for k, v := range r.Header {
			lowerCaseHeader[strings.ToLower(k)] = v
		}
		logger.Info("headers:", lowerCaseHeader)
		req.Header = lowerCaseHeader
		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			logger.Error("HTTP get failed with error : %s", err)
		} else {
			logger.Info("HTTP get succeded")
		}
		if resp != nil {
			resp.Write(w)
		}
		logger.Info("Respond in %d milliseconds", delay)
	})
}

func index() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" {
			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return
		}
		//addResponseHeader(w, r)
		w.WriteHeader(http.StatusOK)
		io.WriteString(w, "Hello, World!")
	})
}

func healthz() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		//addResponseHeader(w, r)
		w.WriteHeader(http.StatusOK)
		io.WriteString(w, "ok")
		logger.Info("This is a healthz test!")
	})
}

func badService() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		//addResponseHeader(w, r)
		w.WriteHeader(http.StatusServiceUnavailable)
		io.WriteString(w, "Bad service")
		logger.Error("Error happen: Bad service!")
	})
}

func logging(logWriter *log.Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()
			wrapped := wrapResponseWriter(w)
			next.ServeHTTP(wrapped, r)
			clientIP := getCurrentIP(r)
			logWriter.Println(clientIP, wrapped.status, r.Method, r.URL.Path, time.Since(start))
		}
		return http.HandlerFunc(fn)
	}
}

func getCurrentIP(r *http.Request) string {
	xForwardedFor := r.Header.Get("X-Forwarded-For")
	ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
	if ip != "" {
		return ip
	}
	ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
	if ip != "" {
		return ip
	}
	if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
		return ip
	}
	return ""
}
